package com.molichuxing.framework.utils;

import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * pdf文档转图片
 * 第三方依赖：
 * <dependency>
 * <groupId>org.apache.pdfbox</groupId>
 * <artifactId>pdfbox</artifactId>
 * <version>2.0.8</version>
 * </dependency>
 *
 * @Author zoumingyu
 * @Date 2020年05月25日
 */
public class PdfToImageUtil {
	private static final Logger LOGGER = LoggerFactory.getLogger(PdfToImageUtil.class);

	private static final String DOT = ".";
	// 图片默认格式 - png
	public static final String DEFAULT_FORMAT = "png";
	// 图片默认分辨率 - 96f
	public static final float DEFAULT_DPI = 96f;

	/**
	 * pdf转一张图片(png图片)
	 *
	 * @param srcPath pdf转换源文件路径
	 * @throws IOException io异常
	 */
	public static String pdfToImage(String srcPath) throws IOException {
		List<String> imgPaths = pdfToImage(srcPath, null, null, DEFAULT_FORMAT, DEFAULT_DPI, true);
		return (imgPaths == null || imgPaths.isEmpty()) ? null : imgPaths.get(0);
	}

	/**
	 * pdf转一张图片
	 *
	 * @param srcPath pdf转换源文件路径
	 * @param imgName 图片名称，为空时默认源文件名称
	 * @param format  待输出图片的格式，默认png
	 * @throws IOException io异常
	 */
	public static String pdfToImage(String srcPath, String imgName, String format) throws IOException {
		List<String> imgPaths = pdfToImage(srcPath, null, imgName, format, DEFAULT_DPI, true);
		return (imgPaths == null || imgPaths.isEmpty()) ? null : imgPaths.get(0);
	}

	/**
	 * pdf转图片
	 *
	 * @param srcPath  pdf转换源文件路径
	 * @param destPath 图片输出目标路径，为空时默认源文件所在的文件夹
	 * @param imgName  图片名称，为空时默认源文件名称
	 * @param format   待输出图片的格式，默认png
	 * @param dpi      分辨率
	 * @param merge    是否合并为一张，默认为每页生成一张图片
	 * @throws IOException io异常
	 */
	public static List<String> pdfToImage(String srcPath, String destPath, String imgName, String format, Float dpi,
	                                      Boolean merge) throws IOException {
		return pdfToImage(new File(srcPath), destPath, imgName, format, dpi == null ? DEFAULT_DPI : dpi, merge == null ?
				false : merge);
	}

	/**
	 * pdf转一张图片(png图片)
	 *
	 * @param srcFile pdf转换源文件
	 * @throws IOException io异常
	 */
	public static String pdfToImage(File srcFile) throws IOException {
		List<String> imgPaths = pdfToImage(srcFile, null, null, DEFAULT_FORMAT, DEFAULT_DPI, true);
		return (imgPaths == null || imgPaths.isEmpty()) ? null : imgPaths.get(0);
	}

	/**
	 * pdf转一张图片(png图片)
	 *
	 * @param srcFile  pdf转换源文件
	 * @param destPath 图片输出目标路径，为空时默认源文件所在的文件夹
	 * @param imgName  图片名称，为空时默认源文件名称
	 * @throws IOException io异常
	 */
	public static String pdfToImage(File srcFile, String destPath, String imgName) throws IOException {
		List<String> imgPaths = pdfToImage(srcFile, destPath, imgName, DEFAULT_FORMAT, DEFAULT_DPI, true);
		return (imgPaths == null || imgPaths.isEmpty()) ? null : imgPaths.get(0);
	}

	/**
	 * pdf转图片
	 *
	 * @param srcFile  pdf转换源文件
	 * @param destPath 图片输出目标路径，为空时默认源文件所在的文件夹
	 * @param imgName  图片名称，为空时默认源文件名称
	 * @param format   待输出图片的格式，默认png
	 * @param dpi      分辨率
	 * @param merge    是否合并为一张
	 * @throws IOException io异常
	 */
	public static List<String> pdfToImage(File srcFile, String destPath, String imgName, String format, float dpi,
	                                      boolean merge) throws IOException {
		// 默认输出路径为源文件所在文件夹
		if (destPath == null || destPath.isEmpty()) {
			destPath = srcFile.getParent();
		} else {
			File destFolder = new File(destPath);
			if (!destFolder.exists()) {
				// 不存在，新增
				destFolder.mkdirs();
			}
		}
		if (imgName == null || imgName.isEmpty()) {
			String pdfFileName = srcFile.getName();
			imgName = pdfFileName.substring(0, pdfFileName.indexOf(DOT));
		}

		return pdfToImage(pdfToBufferedImage(srcFile, dpi), destPath, imgName, format, merge);
	}

	/**
	 * pdf文件转一张图片输出流 （png图片）
	 *
	 * @param srcFile pdf转换源文件
	 * @param destOs  图片输出流
	 * @throws IOException io异常
	 */
	public static void pdfToImage(File srcFile, OutputStream destOs) throws IOException {
		pdfToImage(srcFile, destOs, DEFAULT_FORMAT, DEFAULT_DPI);
	}

	/**
	 * pdf文件转一张图片输出流
	 *
	 * @param srcFile pdf转换源文件
	 * @param destOs  图片输出流
	 * @param format  待输出图片的格式，默认png
	 * @param dpi     分辨率
	 * @throws IOException io异常
	 */
	public static void pdfToImage(File srcFile, OutputStream destOs, String format, float dpi) throws IOException {
		pdfToImage(pdfToBufferedImage(srcFile, dpi), destOs, format);
	}


	/**
	 * pdf输入流转一张图片输出流 （png图片）
	 *
	 * @param srcIs  pdf转换源文件流
	 * @param destOs 图片输出流
	 * @throws IOException io异常
	 */
	public static void pdfToImage(InputStream srcIs, OutputStream destOs) throws IOException {
		pdfToImage(srcIs, destOs, DEFAULT_FORMAT, DEFAULT_DPI);
	}

	/**
	 * pdf输入流转一张图片输出流
	 *
	 * @param srcIs  pdf转换源文件流
	 * @param destOs 图片输出流
	 * @param format 待输出图片的格式，默认png
	 * @param dpi    分辨率
	 * @throws IOException io异常
	 */
	public static void pdfToImage(InputStream srcIs, OutputStream destOs, String format, float dpi) throws IOException {
		pdfToImage(pdfToBufferedImage(srcIs, dpi), destOs, format);
	}

	/**
	 * pdf byte[]转一张图片（png图片）
	 *
	 * @param srcBytes pdf转换源文件byte[]
	 * @param destPath 图片输出目标路径，不可为空
	 * @param imgName  图片名称，不可为空
	 * @return String
	 * @throws IOException io异常
	 */
	public static String pdfToImage(byte[] srcBytes, String destPath, String imgName) throws IOException {
		List<String> imgPaths = pdfToImage(pdfToBufferedImage(srcBytes, DEFAULT_DPI), destPath, imgName, DEFAULT_FORMAT, true);
		return (imgPaths == null || imgPaths.isEmpty()) ? null : imgPaths.get(0);
	}

	/**
	 * pdf byte[]转图片
	 *
	 * @param srcBytes pdf转换源文件byte[]
	 * @param destPath 图片输出目标路径，不可为空
	 * @param imgName  图片名称，不可为空
	 * @param format   待输出图片的格式，默认png
	 * @param dpi      分辨率
	 * @param merge    是否合并为一张
	 * @return List
	 * @throws IOException io异常
	 */
	public static List<String> pdfToImage(byte[] srcBytes, String destPath, String imgName, String format, float dpi,
	                                      boolean merge) throws IOException {
		if (destPath == null || destPath.isEmpty()) {
			return null;
		} else {
			File destFolder = new File(destPath);
			if (!destFolder.exists()) {
				// 不存在，新增
				destFolder.mkdirs();
			}
		}

		return pdfToImage(pdfToBufferedImage(srcBytes, dpi), destPath, imgName, format, merge);
	}

	/**
	 * pdf byte[]转一张图片输出流 （png图片）
	 *
	 * @param srcBytes pdf转换源文件byte[]
	 * @param destOs   图片输出流
	 * @throws IOException io异常
	 */
	public static void pdfToImage(byte[] srcBytes, OutputStream destOs) throws IOException {
		pdfToImage(srcBytes, destOs, DEFAULT_FORMAT, DEFAULT_DPI);
	}

	/**
	 * pdf byte[]转一张图片输出流
	 *
	 * @param srcBytes pdf转换源文件byte[]
	 * @param destOs   图片输出流
	 * @param format   待输出图片的格式，默认png
	 * @param dpi      分辨率
	 * @throws IOException io异常
	 */
	public static void pdfToImage(byte[] srcBytes, OutputStream destOs, String format, float dpi) throws IOException {
		pdfToImage(pdfToBufferedImage(srcBytes, dpi), destOs, format);
	}

	/**
	 * @param images images
	 * @param destOs 输出流
	 * @param format 待输出图片的格式，默认png
	 * @throws IOException io异常
	 */
	private static void pdfToImage(List<BufferedImage> images, OutputStream destOs, String format) throws IOException {
		if (images == null || images.isEmpty()) {
			return;
		}
		// 合并多张图片为一张
		BufferedImage image = mergeImages(images);
		// 将转换后的图片存入输出流
		ImageIO.write(image, format == null ? DEFAULT_FORMAT : format, destOs);
	}

	/**
	 * @param images   images
	 * @param destPath 图片输出目标路径，为空时默认源文件所在的文件夹
	 * @param imgName  图片名称，为空时默认源文件名称
	 * @param format   待输出图片的格式，默认png
	 * @param merge    是否合并为一张
	 * @return List
	 * @throws IOException io异常
	 */
	private static List<String> pdfToImage(List<BufferedImage> images, String destPath, String imgName, String format,
	                                       boolean merge) throws IOException {
		List<String> imgPaths;
		if (images == null || images.isEmpty()) {
			return null;
		}
		if (imgName == null || imgName.isEmpty()) {
			return null;
		} else if (imgName.contains(DOT)) {
			imgName = imgName.substring(0, imgName.indexOf(DOT));
		}
		// 默认为 png
		format = (format == null || format.isEmpty()) ? DEFAULT_FORMAT : format;
		// 获取 pdf 转换后图片名称
		StringBuilder sb = new StringBuilder();
		// 是否合并为一张
		if (merge) {
			// 合并多张图片为一张
			BufferedImage image = mergeImages(images);
			images = null;
			// 图片路径
			sb.append(destPath).append(File.separator).append(imgName).append(DOT).append(format);
			// 保存图片信息
			ImageIO.write(image, format, new File(sb.toString()));
			imgPaths = Arrays.asList(sb.toString());
		} else {
			int len = images.size();
			imgPaths = new ArrayList<>(len);
			// 保存图片信息
			for (int i = 0; i < len; i++) {
				// 图片路径
				sb.append(destPath).append(File.separator).append(imgName).append("_")
						.append(String.format("%04d", i + 1)).append(".").append(format);
				// 保存图片信息，输出格式: [文件夹路径]/[pdf文件名]_0001.png
				ImageIO.write(images.get(i), format, new File(sb.toString()));

				imgPaths.add(sb.toString());
				sb.setLength(0);
			}
		}
		return imgPaths;
	}

	private static BufferedImage mergeImages(@NotNull List<BufferedImage> images) {
		int width = 0, height = 0;
		for (BufferedImage image : images) {
			width = Math.max(image.getWidth(), width);
			height += image.getHeight();
		}
		BufferedImage pdfImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
		Graphics2D g2d = pdfImage.createGraphics();
		height = 0;
		for (BufferedImage image : images) {
			g2d.drawImage(image, (width - image.getWidth()) / 2, height, image.getWidth(), image.getHeight(), null);
			height += image.getHeight();
		}
		g2d.dispose();
		return pdfImage;
	}

	private static List<BufferedImage> pdfToBufferedImage(@NotNull File file, float dpi) throws IOException {
		List<BufferedImage> imgList;
		PDDocument pdDocument = null;
		try {
			pdDocument = PDDocument.load(file);
			imgList = pdfToBufferedImage(pdDocument, dpi);
		} catch (IOException e) {
			LOGGER.error("将pdf页面转换为图像失败(file)", e);
			throw e;
		} finally {
			IOUtils.closeQuietly(pdDocument);
		}
		return imgList;
	}

	private static List<BufferedImage> pdfToBufferedImage(byte[] bytes, float dpi) throws IOException {
		List<BufferedImage> imgList;
		PDDocument pdDocument = null;
		try {
			pdDocument = PDDocument.load(bytes);
			imgList = pdfToBufferedImage(pdDocument, dpi);
		} catch (IOException e) {
			LOGGER.error("将pdf页面转换为图像失败(bytes)", e);
			throw e;
		} finally {
			IOUtils.closeQuietly(pdDocument);
		}
		return imgList;
	}

	private static List<BufferedImage> pdfToBufferedImage(@NotNull InputStream is, float dpi) throws IOException {
		List<BufferedImage> imgList;
		PDDocument pdDocument = null;
		try {
			pdDocument = PDDocument.load(is);
			imgList = pdfToBufferedImage(pdDocument, dpi);
		} catch (IOException e) {
			LOGGER.error("将pdf页面转换为图像失败(is)", e);
			throw e;
		} finally {
			IOUtils.closeQuietly(pdDocument);
		}
		return imgList;
	}

	private static List<BufferedImage> pdfToBufferedImage(@NotNull PDDocument pdDocument, float dpi) throws IOException {
		List<BufferedImage> imgList;
		BufferedImage image;
		try {
			PDFRenderer renderer = new PDFRenderer(pdDocument);
			int numPages = pdDocument.getNumberOfPages();
			imgList = new ArrayList<>();
			for (int i = 0; i < numPages; i++) {
				image = renderer.renderImageWithDPI(i, dpi);
				if (null != image) {
					imgList.add(image);
				}
			}
		} catch (IOException e) {
			LOGGER.error("将pdf页面转换为图像失败", e);
			throw e;
		}
		return imgList;
	}

	private static StringBuilder getPdfImgName(File srcFile, String imgName) {
		StringBuilder sb = new StringBuilder();
		if (imgName == null || imgName.isEmpty()) {
			String pdfFileName = srcFile.getName();
			sb.append(pdfFileName.substring(0, pdfFileName.indexOf(DOT)));
		} else if (imgName.indexOf(DOT) >= 0) {
			sb.append(imgName.substring(0, imgName.indexOf(DOT)));
		} else {
			sb.append(imgName);
		}

		return sb;
	}
}
